/*****************************
* torqueBiped Rig
******************************
* Torque-side, I've configured it so that a "udp controller" can have up to 16 analog inputs...
* The entire body, then, is broken down into three separate controllers (order doesn't matter)
* 1: Body (15) -> Spine 1-4 (HPB) and Neck (HPB)
* 2: Arms (10) -> Right Shoulder (HPB), R Elbow (P), R Hand (P), ditto for Left   <SWAP>
* 3: Legs (12) -> Root, Right Hip (HPB), R Knee (P), R Ankle (P), R Ball (P), ditto for Left  <SWAP>
*
* RootHeight is placed with the legs because it most often needs to go up/down if you're doing some kind of
* squat or jump
******************************
* Eventually, this should be a subclass of customRig instead of xbcPop.  These rigs predate
* the customRig (and their development led to the whole customRig idea anyway).  As there is
* not really time to recode this one and make it "pretty"--I'll settle for making it work!
*******************************/
class torqueBipedRig extends xbcPop
{
  int activePiece = 0;  //0 = none! 1/2/3 = Arms/Legs/Body
  
  //these will hold Point2D...only instead of X/Y it's Millisecond/Value
  ArrayList[] ARMDATA = new ArrayList[10];
  ArrayList[] LEGDATA = new ArrayList[13];
  ArrayList[] BODYDATA = new ArrayList[15];
  
  int[] ARMANALOG  = new int[10];
  int[] LEGANALOG  = new int[13];
  int[] BODYANALOG = new int[15];
  
  boolean[] newArmData  = new boolean[10];
  boolean[] newLegData  = new boolean[13];
  boolean[] newBodyData = new boolean[15];
  
  customSlider[] arms;
  customSlider[] body;
  customSlider[] legs;
  
  int bodyX = w/2;
  int bodyY = 0;

  int controlDrag = -1;
  
  //TEMPORARY HACKS!
  int numActive;
  ArrayList[] CUSTOMDATA;
  int[] CUSTOMANALOG;
  int[] newCustomData;
  customSlider[] custom;
  LaunchButton export;
  
  torqueBipedRig(int f) //filenum
  {
    super(f);
    pg = createGraphics(width,height,JAVA2D);  //this one needs to be bigger...
    x = 0; y = 0;
    w = width; h = height;
    
    export = new LaunchButton("Save",new Message(true,ACTIVELAYER+1,0),0,0,width-80,5,50,25);    
    
    ANALOG = new int[37];
    DIGITAL = new boolean[] {false};
    invertSlider = new boolean[] {false,true,false,true,true};
    
    Arrays.fill(ARMANALOG,127);
    Arrays.fill(LEGANALOG,127);
    Arrays.fill(BODYANALOG,127);
    
    Arrays.fill(newArmData,true);
    Arrays.fill(newLegData,true);
    Arrays.fill(newBodyData,true);
   
    dataX = w/2; 
    dataY = h-50;
    
    bodyX = w/2;
    bodyY = 150;    
    
    arms = new customSlider[10];
    int[] armsX = {20,140,260,140,140,420,540,660,540,540};
    int[] armsY = {40,40,40,160,280,40,40,40,160,280};
    String[] armsL = {"lArm H", "lArm P", "lArm B","lElbow P","lHand P","rArm H", "rArm P", "rArm B","rElbow P","rHand P"};
    for(int i = 0; i < arms.length; i++)
    {
      arms[i] = new customSlider(armsX[i],armsY[i],armsL[i]);
      ARMDATA[i] = new ArrayList();
    }

    legs = new customSlider[13];
    int[] legsX = {340,20,140,140,140,140,20,540,540,660,540,540,660};
    int[] legsY = {20,120,120,40,240,360,360,40,120,120,240,360,360};
    String[] legsL = {"root Y","lLeg H", "lLeg P", "lLeg B","lKnee P","lAnkle P","lBall P","rLeg H", "rLeg P", "rLeg B","rKnee P","rAnkle P","rBall P"};
    for(int i = 0; i < legs.length; i++)
    {
      legs[i] = new customSlider(legsX[i],legsY[i],legsL[i]);
      LEGDATA[i] = new ArrayList();
    }
    

    body = new customSlider[15];
    int[] bodyX = {20,150,280,20,150,280,20,150,280,20,150,280,20,150,280};
    int[] bodyY = {380,380,380,290,290,290,200,200,200,110,110,110,20,20,20};
    String[] bodyL = {"Spine01 H","Spine01 P", "Spine01 B","Spine02 H","Spine02 P", "Spine02 B","Spine03 H","Spine03 P", "Spine03 B","Spine04 H","Spine04 P", "Spine04 B", "Neck H","Neck P","Neck B"};
    for(int i = 0; i < body.length; i++)
    {
      body[i] = new customSlider(bodyX[i],bodyY[i],bodyL[i]);
      BODYDATA[i] = new ArrayList();
    }

  }
  
  void mMessage(Message m)
  {
    if(m.target == 0)
    {
      prepWrite();
    }
  }
  

  void controlDraw()
  {
    //body template!
    pg.stroke(BUTTONSTROKE);
    pg.pushMatrix();
    pg.translate(bodyX,bodyY);
    pg.fill(BUTTONFILL);    
    if((mouseX > bodyX-15 && mouseX < bodyX+15 && mouseY > bodyY && mouseY < bodyY+30)
      || (mouseX > bodyX-30 && mouseX < bodyX + 30 && mouseY > bodyY+30 && mouseY < bodyY+130) )
    {
      pg.fill(BUTTONOVER);
      if(mousePressed || activePiece == 3) pg.fill(BUTTONDOWN);
    }
    pg.rect(-15,0,30,30);
    pg.rect(-30,30,60,100);
    
    pg.fill(BUTTONFILL);    
    if((mouseX > bodyX-45 && mouseX < bodyX-30 && mouseY > bodyY+30 && mouseY < bodyY+130)
      || (mouseX > bodyX+30 && mouseX < bodyX + 45 && mouseY > bodyY+30 && mouseY < bodyY+130) )
    {
      pg.fill(BUTTONOVER);
      if(mousePressed || activePiece == 1) pg.fill(BUTTONDOWN);
    }
    pg.rect(-45,30,15,100);
    pg.rect(30,30,15,100);  
  
    pg.fill(BUTTONFILL);    
    if((mouseX > bodyX-25 && mouseX < bodyX+25 && mouseY > bodyY+130 && mouseY < bodyY+195))
    {
      pg.fill(BUTTONOVER);
      if(mousePressed || activePiece == 2) pg.fill(BUTTONDOWN);
    }
    pg.rect(-25,130,25,65);
    pg.rect(0,130,25,65);
    
    //okay...body drawn...now draw the DragButtonThings for the 5 XBOX inputs
    for(int i = 0; i < 5; i++)
    {
      pg.fill(BUTTONFILL);    
      if(mouseX-bodyX > -75 + 30*i && mouseX-bodyX < -45 + 30*i && mouseY-bodyY > 205 && mouseY-bodyY < 235) pg.fill(BUTTONOVER);
      if(controlDrag == i) pg.fill(BUTTONDOWN);
      pg.rect(-75 + 30*i,205,30,30);
    }
    
    pg.popMatrix();   
    
    for(int i = 0; i < arms.length; i++)
    {
      if(arms[i].input != -1) arms[i].setSlider(slider[arms[i].input].getValue()*(invertSlider[arms[i].input]?-1:1));
      if(activePiece == 1) arms[i].mDraw(pg);
      ARMANALOG[i] = getByteValue(arms[i].value);
    }
   
    for(int i = 0; i < legs.length; i++)
    {
      if(legs[i].input != -1) legs[i].setSlider(slider[legs[i].input].getValue()*(invertSlider[legs[i].input]?-1:1));
      if(activePiece == 2) legs[i].mDraw(pg);
      LEGANALOG[i] = getByteValue(legs[i].value);      
    }        
    for(int i = 0; i < body.length; i++)
    {
      if(body[i].input != -1) body[i].setSlider(slider[body[i].input].getValue()*(invertSlider[body[i].input]?-1:1));
      if(activePiece == 3) body[i].mDraw(pg);
      BODYANALOG[i] = getByteValue(body[i].value);
    }
    
    if(controlDrag >= 0)
    {
      pg.stroke(255);
      pg.line(bodyX-60 + 30*controlDrag,bodyY+220,mouseX,mouseY);
    }
  }
  
  void closeDraw()
  {
    //draw the close button
    pg.textFont(font,18);
    pg.stroke(BUTTONSTROKE);
    pg.strokeWeight(1);
    if(mouseX > x+w-30 && mouseX < x+w-5 && mouseY > y+5 && mouseY < y+25)
    {
      if(mousePressed) pg.fill(BUTTONDOWN);
      else pg.fill(BUTTONOVER);
    }else{
      pg.fill(BUTTONFILL);
    }
    pg.rect(w-30,5,25,25);
    pg.fill(TEXTCOLOR);
    pg.text("X",w-22,25);  
    
    export.mDraw(pg);
      
    
    if(recording){
      pg.fill(255,0,0);
      pg.ellipse(w-30,h-30,30,30);
      
      for(int i = 0; i < arms.length; i++)
      {
        if(arms[i].active)
        {
          if(newArmData[i])  //is this the first time this channel has been modified?
          {
            newArmData[i] = false;
            ARMDATA[i] = new ArrayList();
            ARMDATA[i].add(new Point(0,127));
          }
          Point p = (Point)ARMDATA[i].get(ARMDATA[i].size() - 1);
          if(p.y != getByteValue(arms[i].value)) ARMDATA[i].add(new Point(millis()-startTime,getByteValue(arms[i].value)));
        }else{
          //if we're inactive, use the previously recorded value!
          float oldVal = getValueAtTime(1,i,millis()-startTime);
          ARMANALOG[i] = (int)oldVal;    //why wait for another cycle?
          oldVal = (oldVal - 127.0)/128.0;
          arms[i].setSlider(oldVal);            
        }
      }
      
      for(int i = 0; i < legs.length; i++)
      {
        if(legs[i].active)
        {
          if(newLegData[i])  //is this the first time this channel has been modified?
          {
            newLegData[i] = false;
            LEGDATA[i] = new ArrayList();
            LEGDATA[i].add(new Point(0,127));
          }
          Point p = (Point)LEGDATA[i].get(LEGDATA[i].size() - 1);
          if(p.y != getByteValue(legs[i].value)) LEGDATA[i].add(new Point(millis()-startTime,getByteValue(legs[i].value)));
        }else{
          //if we're inactive, use the previously recorded value!
          float oldVal = getValueAtTime(2,i,millis()-startTime);
          LEGANALOG[i] = (int)oldVal;
          oldVal = (oldVal - 127.0)/128.0;
          legs[i].setSlider(oldVal);            
        }
      }

      for(int i = 0; i < body.length; i++)
      {
        if(body[i].active)
        {
          if(newBodyData[i])  //is this the first time this channel has been modified?
          {
            newBodyData[i] = false;
            BODYDATA[i] = new ArrayList();
            BODYDATA[i].add(new Point(0,127));
          }
          Point p = (Point)BODYDATA[i].get(BODYDATA[i].size() - 1);
          if(p.y != getByteValue(body[i].value)) BODYDATA[i].add(new Point(millis()-startTime,getByteValue(body[i].value)));
        }else{
          //if we're inactive, use the previously recorded value!
          float oldVal = getValueAtTime(3,i,millis()-startTime);
          BODYANALOG[i] = (int)oldVal;
          oldVal = (oldVal - 127.0)/128.0;
          body[i].setSlider(oldVal);
        }
      }
      pg.fill(255);
    }

    if(transmit)
    {
      //update every 1/20 of a second ATM...
      if(millis() - LASTUPDATE > 50){
        if(activePiece == 1) transmitUDP(0,DIGITAL, ARMANALOG);
        if(activePiece == 2) transmitUDP(1,DIGITAL, LEGANALOG);
        if(activePiece == 3) transmitUDP(2,DIGITAL,BODYANALOG);          
        LASTUPDATE = millis(); 
      }
    }

    pg.endDraw();
    pg.modified = true;
  }
  
  int getValueAtTime(int whichRig, int whichValue, int whichTime)
  {
    if(whichTime < 0) return 127;  //homey don't play that
    
    if(whichRig == 1 && ARMDATA[whichValue].size() > 0)
    {
      Point lastPoint = null;
      for(int i = 0; i < ARMDATA[whichValue].size(); i++)
      {
        Point p = (Point)ARMDATA[whichValue].get(i);
        //if I hit it on the nose...return this value!
        if(p.x == whichTime) return p.y;
        
        //if i'm at the end of the line...return the last value!
        if(ARMDATA[whichValue].size()-1 == i && p.x <= whichTime) return p.y;   
        
        //if I passed it, return the one before me!
        if(p.x > whichTime && lastPoint != null) return lastPoint.y;
        lastPoint = p;
      }
    }

    if(whichRig == 2 && LEGDATA[whichValue].size() > 0)
    {
      Point lastPoint = null;
      for(int i = 0; i < LEGDATA[whichValue].size(); i++)
      {
        Point p = (Point)LEGDATA[whichValue].get(i);
        //if I hit it on the nose...return this value!
        if(p.x == whichTime) return p.y;
        
        //if i'm at the end of the line...return the last value!
        if(LEGDATA[whichValue].size()-1 == i && p.x <= whichTime) return p.y;   
        
        //if I passed it, return the one before me!
        if(p.x > whichTime && lastPoint != null) return lastPoint.y;
        lastPoint = p;
      }
    }    

    if(whichRig == 3 && BODYDATA[whichValue].size() > 0)
    {
      Point lastPoint = null;
      for(int i = 0; i < BODYDATA[whichValue].size(); i++)
      {
        Point p = (Point)BODYDATA[whichValue].get(i);
        //if I hit it on the nose...return this value!
        if(p.x == whichTime) return p.y;
        
        //if i'm at the end of the line...return the last value!
        if(BODYDATA[whichValue].size()-1 == i && p.x <= whichTime) return p.y;   
        
        //if I passed it, return the one before me!
        if(p.x > whichTime && lastPoint != null) return lastPoint.y;
        lastPoint = p;
      }
    }
    
    //if I asked for the value of an empty arraylist...
    return 127;
  }

  void mPress()
  {
    super.mPress();
    if(activePiece == 1)
    {
      for(int i = 0; i < arms.length; i++)
      {
        arms[i].mPress();
      }  
    }else if(activePiece == 2){
      for(int i = 0; i < legs.length; i++)
      {
        legs[i].mPress();
      }        
    }else if(activePiece == 3){
      for(int i = 0; i < body.length; i++)
      {
        body[i].mPress();
      }          
    }

    controlDrag = -1;
    for(int i = 0; i < 5; i++)
    {
      if(mouseX-bodyX > -75 + 30*i && mouseX-bodyX < -45 + 30*i && mouseY-bodyY > 205 && mouseY-bodyY < 235)
      {
        if(activePiece != 0) controlDrag = i;
      }
    }    
  }
  
  void mRelease()
  {
    super.mRelease();

    export.mRelease();
    //give the bodySelector first crack at it!
    if((mouseX > bodyX-15 && mouseX < bodyX+15 && mouseY > bodyY && mouseY < bodyY+30)
      || (mouseX > bodyX-30 && mouseX < bodyX + 30 && mouseY > bodyY+30 && mouseY < bodyY+130) )
      {
      activePiece = 3;
      bodyX = 600;
      bodyY = 150;      
      dataX = 600;
      dataY = w-50;
    }else if((mouseX > bodyX-45 && mouseX < bodyX-30 && mouseY > bodyY+30 && mouseY < bodyY+130)
      || (mouseX > bodyX+30 && mouseX < bodyX + 45 && mouseY > bodyY+30 && mouseY < bodyY+130) )
    {
      activePiece = 1;
      bodyX = 400;
      bodyY = 150;      
      dataX = 400;   
      dataY = h-50;   
    }else if((mouseX > bodyX-25 && mouseX < bodyX+25 && mouseY > bodyY+130 && mouseY < bodyY+195))
    {
      activePiece = 2;
      bodyX = 400;
      bodyY = 150;   
      dataX = 400;
      dataY = h-50;
    }else{
      //ok, body didn't grab it...check the sliders!      
      if(activePiece == 1)
      {
        for(int i = 0; i < arms.length; i++)
        {
          arms[i].mRelease(controlDrag);
        }        
      }else if(activePiece == 2){
        for(int i = 0; i < legs.length; i++)
        {
          legs[i].mRelease(controlDrag);
        }        
      }else if(activePiece == 3){
        for(int i = 0; i < body.length; i++)
        {
          body[i].mRelease(controlDrag);
        }          
      }   
    }
    controlDrag = -1; 
  }
  
  void mKeyUp()
  {
    
    if(key == ' '){
      recording = !recording;
      if(recording){
        startTime = millis();
        
        Arrays.fill(newArmData,true);
        Arrays.fill(newLegData,true);
        Arrays.fill(newBodyData,true);        
        
        for(int i = 0; i < arms.length; i++)
        {
          if(arms[i].active)
          {
            ARMDATA[i] = new ArrayList();
            ARMDATA[i].add(new Point(0,getByteValue(arms[i].value)));
            newArmData[i] = false;
          }
        }
        for(int i = 0; i < legs.length; i++)
        {
          if(legs[i].active)
          {
            LEGDATA[i] = new ArrayList();
            LEGDATA[i].add(new Point(0,getByteValue(legs[i].value)));
            newLegData[i] = false;
          }
        }
        for(int i = 0; i < body.length; i++)
        {
          if(body[i].active){
            BODYDATA[i] = new ArrayList();
            BODYDATA[i].add(new Point(0,getByteValue(body[i].value)));
            newBodyData[i] = false;
          }
        }       

      }else{
        //write to a file!
        //okay, not really...trick the superclass into thinking this is a well-behaving
        //controller by sticking our piecemeal data into the standard data ArrayList
        println("Recording done!");    
      }
    }

    if(key == 't'){
      transmit = !transmit;
    }

    if(keyCode == ESC){
      DONE = true;
      //tell the parent layer which fileNum we got up to
      messages.add(new Message(fileNum + "", ACTIVELAYER-1,21));
    }
    
    
  }
  
  
  void prepWrite()
  {
    //trick the superclass into thinking this is a well-behaving controller by 
    //sticking our piecemeal data into the standard data ArrayList
    if(activePiece == 1)
    {
      numActive = ARMANALOG.length;
      CUSTOMDATA = ARMDATA;
    }else if(activePiece == 2){
      numActive = LEGANALOG.length;
      CUSTOMDATA = LEGDATA;
    }else if(activePiece == 3){
      numActive = BODYANALOG.length;
      CUSTOMDATA = BODYDATA;      
    }else{
      //shouldn't be here!
      return;
    }
    
    
    ArrayList piecemeal = new ArrayList();
    ANALOG = new int[numActive];

    for(int i = 0; i < numActive; i++)
    {
      //if(CUSTOMDATA[i].size() == 0) println("Custom " + i + ": No points!");
      for(int j = 0; j < CUSTOMDATA[i].size(); j++)
      {
        Point p = (Point)CUSTOMDATA[i].get(j);
        piecemeal.add(constructDataPoint(p.x));
        //println("Custom " + i + ": " + p.x + " " + p.y);
      }
    }

    //okay...now we have all of our dataPoints...now we just need to put them in the right order!
    //this could be optimized...
    data = new ArrayList();
    while(piecemeal.size() > 0)
    {
      dataPoint p = null;
      for(int i = 0; i < piecemeal.size(); i++)
      {
        dataPoint d = (dataPoint)piecemeal.get(i);
        if(p != null && p.TIME == d.TIME)
        {
          piecemeal.remove(d);
          i--;
        }else if(p == null || p.TIME > d.TIME){
          p = d;
        }
      }
      piecemeal.remove(p);
      if(!data.contains(p)) data.add(p);
    }

    //You can DO IT!
    if(data.size() > 0) writeState();
    else popWarn("No data!");
  }

  dataPoint constructDataPoint(int whichTime)
  {
    int[] d = new int[] {
      0    };  //digital not implemented (yet!)
    int[] a = new int[numActive];

    for(int i = 0; i < numActive; i++)
    {
      a[i] = getValueAtTime(i,whichTime);
    }

    return new dataPoint(0,whichTime,d,a);    
  }

  int getValueAtTime(int whichValue, int whichTime)
  {
    if(whichTime < 0) return 127;  //homey don't play that

    if(CUSTOMDATA[whichValue].size() > 0)
    {
      Point lastPoint = null;
      for(int i = 0; i < CUSTOMDATA[whichValue].size(); i++)
      {
        Point p = (Point)CUSTOMDATA[whichValue].get(i);
        //if I hit it on the nose...return this value!
        if(p.x == whichTime) return p.y;

        //if i'm at the end of the line...return the last value!
        if(CUSTOMDATA[whichValue].size()-1 == i && p.x <= whichTime) return p.y;   

        //if I passed it, return the one before me!
        if(p.x > whichTime && lastPoint != null) return lastPoint.y;
        lastPoint = p;
      }
    }

    //if I asked for the value of an empty arraylist...
    return 127;
  }  
  
 
}




/**************************
* Torque Face Rig
***************************
* This is a rig for controlling a "Stop Staring" facial rig...
* There are sliders for Mouth Open/Width, Look Up/Side, ULid/LLid verts,
* brow height/tilt, and head tilt.
***************************
* This is also a much more "natural" subclass, as there's only one screen needed.
**************************/

class torqueFaceRig extends customRig
{
  
  
  torqueFaceRig(int f) //filenum
  {
    super(f);
    
    numActive = 10;
    custom = new customSlider[10];
    int[] cX = {10,10,10,10,10,140,140,140,140,140};
    int[] cY = {20,110,200,290,380,20,110,200,290,380};
    String[] cL = {"Mouth H","Mouth V","Look H","Look V","U Lid","L Lid","Brow H","Brow Tilt","Head B","Head P"};
    for(int i = 0; i < custom.length; i++)
    {
      custom[i] = new customSlider(cX[i],cY[i],cL[i]);
      CUSTOMDATA[i] = new ArrayList();
    }
    
    cSelX += 50;
    dataX += 50;
  }
  
  void controlDraw()
  {
    pg.stroke(BUTTONSTROKE);
    pg.pushMatrix();
    pg.translate(cSelX,cSelY);
    for(int i = 0; i < 5; i++)
    {
      pg.fill(BUTTONFILL);    
      if(mouseX-cSelX > -75 + 30*i && mouseX-cSelX < -45 + 30*i && mouseY-cSelY > 205 && mouseY-cSelY < 235) pg.fill(BUTTONOVER);
      if(controlDrag == i) pg.fill(BUTTONDOWN);
      pg.rect(-75 + 30*i,205,30,30);
    }
    pg.popMatrix();   

    export.mDraw(pg);

    for(int i = 0; i < numActive; i++)
    {
      if(custom[i].input != -1) custom[i].setSlider(slider[custom[i].input].getValue()*(invertSlider[custom[i].input]?-1:1));
      custom[i].mDraw(pg);
      CUSTOMANALOG[i] = getByteValue(custom[i].value);
    }

    if(controlDrag >= 0)
    {
      pg.stroke(255);
      pg.line(cSelX-60 + 30*controlDrag,cSelY+220,mouseX,mouseY);
    }
  }

  void mPress()
  {
    super.mPress();
    for(int i = numActive-1; i >= 0; i--)
    {
      if(custom[i].mPress())
      {
        lastClickedOn = i;
        return;
      }
    }

    controlDrag = -1;
    for(int i = 0; i < 5; i++)
    {
      if(mouseX-cSelX > -75 + 30*i && mouseX-cSelX < -45 + 30*i && mouseY-cSelY > 205 && mouseY-cSelY < 235) controlDrag = i;
    }
  }

  void mRelease()
  {
    if(mouseX > x+w-30 && mouseX < x+w-5 && mouseY > y+5 && mouseY < y+25)
    {
      messages.add(new Message(fileNum + "", ACTIVELAYER-1,21));
      DONE = true;      
    } 

    //counts down so the one on top (the last one drawn)
    //gets the first click
    for(int i = 0; i < numActive; i++)
    {
      custom[i].mRelease(controlDrag);
    }        

    export.mRelease();
    controlDrag = -1;    
  }  
  
  
}
